#pragma once

#include "log.hpp"
#include "protocol.hpp"

#include <iostream>
#include <string>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>

using namespace std;

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;

    static const int gbacklog = 5;

    using func_t = function<bool (const HttpRequest&, HttpResponse&)>;

    class HttpServer
    {
    public:
        HttpServer(const uint16_t &port, func_t func)
            : _listensock(-1), _port(port), _func(func)
        {
        }

        ~HttpServer()
        {
        }

        void initServer()
        {
            // 1.创建socket文件套接字
            // _sock = socket(AF_INET, SOCK_STREAM, 0);
            _listensock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket err!");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success!");

            // 2.绑定网络信息
            struct sockaddr_in local;
            local.sin_family = PF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY; // 跟UDP一样，绑定任意IP
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket err!");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success!");

            // 3.设置socket为监听状态
            if (listen(_listensock, gbacklog) < 0) // 第二个参数后面再学
            {
                logMessage(FATAL, "listen socket err!");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success : %d!", _listensock);
        }

        void HandlerHttp(int sock)
        {
            // 1.读到完整的一个http请求
            // 2.反序列化
            // 3.得到一个结构化的HttpRequest -> 调用_func -> 得到一个HttpResponse的结构化数据
            // 4.对HttpResponse进行序列化
            // 5.发送数据

            char buffer[4096];
            HttpRequest req;
            HttpResponse resp;
            size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if(n > 0)
            {
                buffer[n] = 0;
                req.inbuffer = buffer;
                req.parse();
                _func(req, resp);
                send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);
            }
        }

        void run()
        {
            for (;;)
            {
                // 4.获取新链接(注意：获取的前提是链接已经存在)
                // accept的返回值sock，将来才是和client通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                // 这俩参数是我们要知道谁来连我们的输入输出型参数
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next!");
                    continue; // 这里accept失败并不是什么大问题，直接重新获取新链接就行了
                }
                logMessage(NORMAL, "accept a new link success! get new sockfd : %d", sock);

                // version 2 - 多进程版(2种方法)

                pid_t id = fork();
                if (id == 0)
                {
                    // child
                    close(_listensock); // 子进程一定要关闭自己不需要的文件描述符

                    if (fork() > 0)
                        exit(0); // 子进程创建它的子进程(孙子进程)，然后子进程退了，下面waitpid()立马调用，就回收了
                    HandlerHttp(sock);
                    close(sock);
                    exit(0);
                }

                close(sock); // 当然，父进程也要关闭自己不需要的sockfd

                pid_t ret = waitpid(id, nullptr, 0);
                // 非阻塞等待不好写，也不行，因为如果今天一百人来连我，然后后面就无人来了，就会阻塞在accept()那里，就没有回收子进程了
                if (ret > 0)
                {
                    logMessage(NORMAL, "wait success!");
                }
            }
        }

    private:
        int _listensock; // 这个套接字不是用来通信的，是用来监听的！
        int _port;
        func_t _func;
    };

} // namespace server
